# -*- coding:utf-8 -*-
# author:leviwu@tencent.com
# datetime:2021/8/5 20:30:00

import argparse
import importlib.util
import inspect
import os
import re
import sys
import traceback
from pprint import pprint

g_redis_client = None

case_method_re = ['test*', 'test_*']


def analyse_case_dir(root_path: str):
    if not os.path.exists(root_path):
        return []

    all_test_cases = []

    for root, dirs, filenames in os.walk(root_path):
        for filename in filenames:
            if not filename.endswith('.py'):
                continue

            file_path = os.path.join(root, filename)
            test_cases = analyse_py_case(root_path, file_path)
            if test_cases:
                all_test_cases.extend(test_cases)

    return all_test_cases


def analyse_py_case(root_path: str, module_path: str):
    """
    :param root_path:
    :param module_path:
    :return:
    """
    module_rel_path = os.path.relpath(module_path, root_path)
    module_name = ".".join(module_rel_path.replace(os.path.sep, ".").split(".")[:-1])
    test_cases = []

    if root_path not in sys.path:
        sys.path.insert(0, root_path)
    try:
        mod = import_module(module_name, module_path)
    except (FileNotFoundError, ModuleNotFoundError):
        traceback.print_exc()
        return []

    has_hit_names = set()
    mod_members = {}
    for memname, memobj in inspect.getmembers(mod):
        if inspect.isclass(memobj) and issubclass(memobj, object):
            mod_members[memname] = memobj
    for memname, memobj in mod_members.items():
        leaf_obj = memobj
        for other_name, other_obj in mod_members.items():
            if issubclass(other_obj, memobj) and other_obj is not leaf_obj:
                has_hit_names.add(leaf_obj)
                leaf_obj = other_obj
        if leaf_obj not in has_hit_names:
            has_hit_names.add(leaf_obj)
            case_list = get_test_methods(leaf_obj, module_name)
            if case_list:
                test_cases += case_list

    return test_cases


def import_module(module_name, file_path=None):
    module_name = module_name.strip()
    comps = module_name.split(".")
    if len(comps) > 1:
        module_name = comps[-1]
    spec = importlib.util.spec_from_file_location(module_name, file_path)
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod


def get_test_methods(test_case_class, module_name):
    s = []
    for name, value in inspect.getmembers(test_case_class):
        if match_case(name) and callable(getattr(test_case_class, name)):
            if hasattr(value, '__code__') and hasattr(value, '__name__') and hasattr(value, '__qualname__'):
                method_qualified_name = value.__qualname__
                code_name = method_qualified_name.split('.')[-1]
                if not name.startswith(code_name):
                    code_name = name
                s.append({"name": name, "code_name": code_name, 'pkg': module_name})
    return s


def match_case(name):
    for each_r in case_method_re:
        if re.match(each_r, name, re.I):
            return True
    return False


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Execute case analyse.')

    parser.add_argument('-d', '--directory', required=True, type=str, help='group case root directory path')

    args = parser.parse_args()

    pprint(analyse_case_dir(args.directory))
